/*
 * Copyright (c) 2020 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sdsmdg.harjot.vectormaster;

import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.global.resource.solidxml.Attribute;
import ohos.global.resource.solidxml.Node;
import ohos.global.resource.solidxml.SolidXml;
import ohos.javax.xml.parsers.ParserConfigurationException;
import ohos.javax.xml.parsers.SAXParser;
import ohos.javax.xml.parsers.SAXParserFactory;
import ohos.org.xml.sax.Attributes;
import ohos.org.xml.sax.SAXException;
import ohos.org.xml.sax.helpers.DefaultHandler;

import com.sdsmdg.harjot.vectormaster.models.ClipPathModel;
import com.sdsmdg.harjot.vectormaster.models.GroupModel;
import com.sdsmdg.harjot.vectormaster.models.PathModel;
import com.sdsmdg.harjot.vectormaster.models.VectorModel;
import com.sdsmdg.harjot.vectormaster.utilities.LogUtil;
import com.sdsmdg.harjot.vectormaster.utilities.Utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Stack;

/**
 * Vector xml parse class
 */
public class XmlParser {
    private static final String TAG = XmlParser.class.getName();

    private VectorModel vectorModel;
    private Stack<GroupModel> groupModelStack = new Stack<>();

    public VectorModel buildVectorModel(Context context, int xml) {
        ResourceManager manager = context.getResourceManager();
        try {
            SolidXml solidXml = manager.getSolidXml(xml);
            Node root = solidXml.getRoot();
            if (root == null) {
                return vectorModel = null;
            }
            vectorModel = new VectorModel();
            parseNode(root);
            vectorModel.buildTransformMatrices();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotExistException e) {
            e.printStackTrace();
        } catch (WrongTypeException e) {
            e.printStackTrace();
        }
        return vectorModel;
    }

    public void parseNode(Node root) {
        LogUtil.info("xml", " ----------- : " + root.getName());
        if (root.getName().equalsIgnoreCase("vector")) {
            parseRootVector(root);
        } else if (root.getName().equalsIgnoreCase("path")) {
            parsePath(root);
        } else if (root.getName().equalsIgnoreCase("group")) {
            parseGroup(root);
        } else if (root.getName().equalsIgnoreCase("clip-path")) {
            parseClipPath(root);
        }

        if (root.getChild() != null) {
            parseNode(root.getChild());
        }

        if (root.getName().equalsIgnoreCase("group")) {
            LogUtil.info("xml", " ----------- : close " + root.getName());
            GroupModel topGroupModel = groupModelStack.pop();
            if (groupModelStack.size() == 0) {
                topGroupModel.setParent(null);
                vectorModel.addGroupModel(topGroupModel);
            } else {
                topGroupModel.setParent(groupModelStack.peek());
                groupModelStack.peek().addGroupModel(topGroupModel);
            }
        }

        if (root.getSibling() != null) {
            parseNode(root.getSibling());
        }
    }

    private void parseClipPath(Node root) {
        ClipPathModel clipPathModel = new ClipPathModel();
        List<Attribute> attrs = root.getAttributes();

        for (int i = 0; i < attrs.size(); i++) {
            Attribute attribute = attrs.get(i);
            LogUtil.info("xml", " ----------- : A " + attribute.getName() + " : " + attribute.getStringValue());
            if (attribute.getName().equalsIgnoreCase("name")) {
                clipPathModel.setName(attribute.getStringValue());
            } else if (attribute.getName().equalsIgnoreCase("pathData")) {
                clipPathModel.setPathData(attribute.getStringValue());
            }
        }
        clipPathModel.buildPath();

        if (groupModelStack.size() == 0) {
            vectorModel.addClipPathModel(clipPathModel);
        } else {
            groupModelStack.peek().addClipPathModel(clipPathModel);
        }
    }

    private void parseGroup(Node root) {
        GroupModel groupModel = new GroupModel();
        List<Attribute> attrs = root.getAttributes();

        for (int i = 0; i < attrs.size(); i++) {
            Attribute attribute = attrs.get(i);
            LogUtil.info("xml", " ----------- : A " + attribute.getName() + " : " + attribute.getStringValue());
            if (attribute.getName().equalsIgnoreCase("name")) {
                groupModel.setName(attribute.getStringValue());
            } else if (attribute.getName().equalsIgnoreCase("pivotX")) {
                groupModel.setPivotX(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("pivotY")) {
                groupModel.setPivotY(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("rotation")) {
                groupModel.setRotation(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("scaleX")) {
                groupModel.setScaleX(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("scaleY")) {
                groupModel.setScaleY(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("translateX")) {
                groupModel.setTranslateX(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("translateY")) {
                groupModel.setTranslateY(Float.parseFloat(attribute.getStringValue()));
            }
        }
        groupModelStack.push(groupModel);
    }

    private void parsePath(Node root) {
        PathModel pathModel = new PathModel();
        List<Attribute> attrs = root.getAttributes();
        for (int i = 0; i < attrs.size(); i++) {
            Attribute attribute = attrs.get(i);
            LogUtil.info("xml", " ----------- : A " + attribute.getName() + " : " + attribute.getStringValue());
            if (attribute.getName().equalsIgnoreCase("name")) {
                pathModel.setName(attribute.getStringValue());
            } else if (attribute.getName().equalsIgnoreCase("fillAlpha")) {
                pathModel.setFillAlpha(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("fillColor")) {
                pathModel.setFillColor(Utils.getColorFromString(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("fillType")) {
                pathModel.setFillType(Utils.getFillTypeFromString(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("pathData")) {
                pathModel.setPathData(attribute.getStringValue());
            } else if (attribute.getName().equalsIgnoreCase("strokeAlpha")) {
                pathModel.setStrokeAlpha(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("strokeColor")) {
                pathModel.setStrokeColor(Utils.getColorFromString(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("strokeLineCap")) {
                pathModel.setStrokeLineCap(Utils.getLineCapFromString(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("strokeLineJoin")) {
                pathModel.setStrokeLineJoin(Utils.getLineJoinFromString(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("strokeMiterLimit")) {
                pathModel.setStrokeMiterLimit(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("strokeWidth")) {
                pathModel.setStrokeWidth(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("trimPathEnd")) {
                pathModel.setTrimPathEnd(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("trimPathOffset")) {
                pathModel.setTrimPathOffset(Float.parseFloat(attribute.getStringValue()));
            } else if (attribute.getName().equalsIgnoreCase("trimPathStart")) {
                pathModel.setTrimPathStart(Float.parseFloat(attribute.getStringValue()));
            }
        }
        pathModel.buildPath();

        if (groupModelStack.size() == 0) {
            vectorModel.addPathModel(pathModel);
        } else {
            groupModelStack.peek().addPathModel(pathModel);
        }
        vectorModel.getFullpath().addPath(pathModel.getPath());
    }

    private void parseRootVector(Node root) {
        if (vectorModel == null) {
            return;
        }
        List<Attribute> attrs = root.getAttributes();
        for (int i = 0; i < attrs.size(); i++) {
            Attribute attribute = attrs.get(i);
            if ("viewportWidth".equalsIgnoreCase(attribute.getName())) {
                vectorModel.setViewportWidth(Float.parseFloat(attribute.getStringValue()));
            } else if ("viewportHeight".equalsIgnoreCase(attribute.getName())) {
                vectorModel.setViewportHeight(Float.parseFloat(attribute.getStringValue()));
            } else if ("alpha".equalsIgnoreCase(attribute.getName())) {
                vectorModel.setAlpha(Float.parseFloat(attribute.getStringValue()));
            } else if ("name".equalsIgnoreCase(attribute.getName())) {
                vectorModel.setName(attribute.getStringValue());
            } else if ("width".equalsIgnoreCase(attribute.getName())) {
                vectorModel.setWidth(Utils.getFloatFromDimensionString(attribute.getStringValue()));
            } else if ("height".equalsIgnoreCase(attribute.getName())) {
                vectorModel.setHeight(Utils.getFloatFromDimensionString(attribute.getStringValue()));
            }
        }
    }

    /**
     * Parse the vector xml and build the VectorModel
     *
     * @param context context
     * @param path    vector image xml file path
     * @return VectorModel
     */
    public VectorModel buildVectorModel(Context context, String path) {
        if (path == null || path.isEmpty()) {
            vectorModel = null;
            return vectorModel;
        }
        try {
            vectorModel = new VectorModel();

            Stack<GroupModel> groupModelStack = new Stack<>();
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
            DefaultHandler handler = new DefaultHandler() {
                int tempPosition;
                PathModel pathModel = new PathModel();
                GroupModel groupModel = new GroupModel();
                ClipPathModel clipPathModel = new ClipPathModel();

                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes)
                        throws SAXException {
                    LogUtil.info(TAG + "--startElement--", localName + "--" + qName);
                    if (qName.equalsIgnoreCase("vector")) {
                        tempPosition = getAttrPosition(attributes, "viewportWidth");
                        vectorModel.setViewportWidth((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.VECTOR_VIEWPORT_WIDTH);

                        tempPosition = getAttrPosition(attributes, "viewportHeight");
                        LogUtil.info(TAG + "viewportHeight", attributes.getValue(tempPosition));
                        vectorModel.setViewportHeight((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.VECTOR_VIEWPORT_HEIGHT);

                        tempPosition = getAttrPosition(attributes, "alpha");
                        vectorModel.setAlpha((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.VECTOR_ALPHA);

                        tempPosition = getAttrPosition(attributes, "name");
                        vectorModel.setName((tempPosition != -1) ? attributes.getValue(tempPosition) : null);

                        tempPosition = getAttrPosition(attributes, "width");
                        vectorModel.setWidth((tempPosition != -1)
                                ? Utils.getFloatFromDimensionString(attributes.getValue(tempPosition))
                                : DefaultValues.VECTOR_WIDTH);

                        tempPosition = getAttrPosition(attributes, "height");
                        vectorModel.setHeight((tempPosition != -1)
                                ? Utils.getFloatFromDimensionString(attributes.getValue(tempPosition))
                                : DefaultValues.VECTOR_HEIGHT);
                    } else if (qName.equalsIgnoreCase("path")) {
                        LogUtil.info(TAG + "--startElement--path--", localName + "--" + qName);
                        pathModel = new PathModel();

                        tempPosition = getAttrPosition(attributes, "name");
                        pathModel.setName((tempPosition != -1) ? attributes.getValue(tempPosition) : null);

                        tempPosition = getAttrPosition(attributes, "fillAlpha");
                        pathModel.setFillAlpha((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_FILL_ALPHA);

                        tempPosition = getAttrPosition(attributes, "fillColor");
                        pathModel.setFillColor((tempPosition != -1)
                                ? Utils.getColorFromString(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_FILL_COLOR);

                        tempPosition = getAttrPosition(attributes, "fillType");
                        pathModel.setFillType((tempPosition != -1)
                                ? Utils.getFillTypeFromString(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_FILL_TYPE);

                        tempPosition = getAttrPosition(attributes, "pathData");
                        pathModel.setPathData((tempPosition != -1) ? attributes.getValue(tempPosition) : null);

                        tempPosition = getAttrPosition(attributes, "strokeAlpha");
                        pathModel.setStrokeAlpha((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_STROKE_ALPHA);

                        tempPosition = getAttrPosition(attributes, "strokeColor");
                        pathModel.setStrokeColor((tempPosition != -1)
                                ? Utils.getColorFromString(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_STROKE_COLOR);

                        tempPosition = getAttrPosition(attributes, "strokeLineCap");
                        pathModel.setStrokeLineCap((tempPosition != -1)
                                ? Utils.getLineCapFromString(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_STROKE_LINE_CAP);

                        tempPosition = getAttrPosition(attributes, "strokeLineJoin");
                        pathModel.setStrokeLineJoin((tempPosition != -1)
                                ? Utils.getLineJoinFromString(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_STROKE_LINE_JOIN);

                        tempPosition = getAttrPosition(attributes, "strokeMiterLimit");
                        pathModel.setStrokeMiterLimit((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_STROKE_MITER_LIMIT);

                        tempPosition = getAttrPosition(attributes, "strokeWidth");
                        pathModel.setStrokeWidth((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_STROKE_WIDTH);

                        tempPosition = getAttrPosition(attributes, "trimPathEnd");
                        pathModel.setTrimPathEnd((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_TRIM_PATH_END);

                        tempPosition = getAttrPosition(attributes, "trimPathOffset");
                        pathModel.setTrimPathOffset((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_TRIM_PATH_OFFSET);

                        tempPosition = getAttrPosition(attributes, "trimPathStart");
                        pathModel.setTrimPathStart((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.PATH_TRIM_PATH_START);

                        pathModel.buildPath();
                    } else if (qName.equalsIgnoreCase("group")) {
                        LogUtil.info(TAG + "--startElement--group--", localName + "--" + qName);
                        groupModel = new GroupModel();

                        tempPosition = getAttrPosition(attributes, "name");
                        groupModel.setName((tempPosition != -1) ? attributes.getValue(tempPosition) : null);

                        tempPosition = getAttrPosition(attributes, "pivotX");
                        groupModel.setPivotX((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.GROUP_PIVOT_X);

                        tempPosition = getAttrPosition(attributes, "pivotY");
                        groupModel.setPivotY((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.GROUP_PIVOT_Y);

                        tempPosition = getAttrPosition(attributes, "rotation");
                        groupModel.setRotation((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.GROUP_ROTATION);

                        tempPosition = getAttrPosition(attributes, "scaleX");
                        groupModel.setScaleX((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.GROUP_SCALE_X);

                        tempPosition = getAttrPosition(attributes, "scaleY");
                        groupModel.setScaleY((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.GROUP_SCALE_Y);

                        tempPosition = getAttrPosition(attributes, "translateX");
                        groupModel.setTranslateX((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.GROUP_TRANSLATE_X);

                        tempPosition = getAttrPosition(attributes, "translateY");
                        groupModel.setTranslateY((tempPosition != -1)
                                ? Float.parseFloat(attributes.getValue(tempPosition))
                                : DefaultValues.GROUP_TRANSLATE_Y);

                        groupModelStack.push(groupModel);
                    } else if (qName.equalsIgnoreCase("clip-path")) {
                        clipPathModel = new ClipPathModel();

                        tempPosition = getAttrPosition(attributes, "name");
                        clipPathModel.setName((tempPosition != -1) ? attributes.getValue(tempPosition) : null);

                        tempPosition = getAttrPosition(attributes, "pathData");
                        clipPathModel.setPathData((tempPosition != -1) ? attributes.getValue(tempPosition) : null);

                        clipPathModel.buildPath();
                    }
                }

                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException {
                    LogUtil.info(TAG + "--endElement--path--", localName + "--" + qName);
                    if (qName.equalsIgnoreCase("path")) {
                        LogUtil.info(TAG + "--endElement--path--", localName + "--" + qName);
                        if (groupModelStack.size() == 0) {
                            vectorModel.addPathModel(pathModel);
                        } else {
                            groupModelStack.peek().addPathModel(pathModel);
                        }
                        vectorModel.getFullpath().addPath(pathModel.getPath());
                    } else if (qName.equalsIgnoreCase("clip-path")) {
                        LogUtil.info(TAG + "--endElement--clip-path--", localName + "--" + qName);
                        if (groupModelStack.size() == 0) {
                            vectorModel.addClipPathModel(clipPathModel);
                        } else {
                            groupModelStack.peek().addClipPathModel(clipPathModel);
                        }
                    } else if (qName.equalsIgnoreCase("group")) {
                        LogUtil.info(TAG + "--endElement--Group--", localName + "--" + qName);
                        GroupModel topGroupModel = groupModelStack.pop();
                        if (groupModelStack.size() == 0) {
                            topGroupModel.setParent(null);
                            vectorModel.addGroupModel(topGroupModel);
                        } else {
                            topGroupModel.setParent(groupModelStack.peek());
                            groupModelStack.peek().addGroupModel(topGroupModel);
                        }
                    } else if (qName.equalsIgnoreCase("vector")) {
                        vectorModel.buildTransformMatrices();
                    }
                }

                @Override
                public void characters(char[] ch, int start, int length) throws SAXException {
                    super.characters(ch, start, length);
                }
            };

            LogUtil.info(TAG + "--Path--", path);
            InputStream inputStream = context.getResourceManager().getRawFileEntry(path).openRawFile();

            saxParser.parse(inputStream, handler);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            LogUtil.error(TAG, e.getMessage());
        }

        return vectorModel;
    }

    private int getAttrPosition(Attributes xpp, String attrName) {
        for (int i = 0; i < xpp.getLength(); i++) {
            if (xpp.getQName(i).equals("ohos:" + attrName)) {
                return i;
            }
        }
        return -1;
    }
}
